﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NetFrameworkExtensions.Utils
{
  [Serializable]
  public class WeakReadonlyReference<T> : ReadonlyReferenceBase<T>
  {
    private IReadonlyReference<T> innerReference;
    [NonSerialized]
    private WeakReference reference;
    [NonSerialized]
    private object syncLock = new object();
    bool keepAlive;
    T aliveReference;

    public WeakReadonlyReference(T value, bool keepAlive = false)
      : this(new ValueReadonlyReference<T>(value), keepAlive)
    {
    }

    public WeakReadonlyReference(IReadonlyReference<T> innerReference, bool keepAlive = false)
    {
      if (innerReference == null) throw new ArgumentNullException("innerReference");
      this.innerReference = innerReference;
      this.keepAlive = keepAlive;
      if (keepAlive)
        aliveReference = innerReference.Value;
      if (innerReference.CanChange)
        innerReference.ValueChanged += new EventHandler<ValueChangedEventArgs<T>>(innerReference_ValueChanged);
    }

    void innerReference_ValueChanged(object sender, ValueChangedEventArgs<T> e)
    {
      if (keepAlive)
        aliveReference = e.Value;
      reference = null;
      OnValueChanged(e.Value);
    }

    public override T Value
    {
      get
      {
        if (keepAlive) return aliveReference;
        if (reference == null || !reference.IsAlive)
        {
          lock (syncLock)
          {
            if (reference == null)
            {
              var t = innerReference.Value;
              var newRef = new WeakReference(t);
              reference = newRef;
              return t;
            }
            if (!reference.IsAlive)
            {
              var t = innerReference.Value;
              reference.Target = t;
              return t;
            }
          }
        }
        return (T)reference.Target;
      }
    }

    public override bool CanChange
    {
      get { return innerReference.CanChange; }
    }
  }
}
